from datetime import timedelta

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone

from apps.learning_path.models import (
    KnowledgeNode,
    LearningPath,
    PathNode,
    ReviewTask,
    StudyRecord,
)
from apps.learning_path.services import LearningProgressService, ReviewScheduler


class LearningProgressServiceTests(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username='tester', email='tester@example.com', password='pwd123456')

        self.node_prereq = KnowledgeNode.objects.create(
            user=self.user,
            name='Python 基础',
            node_type='concept',
            mastery_level=0,
        )
        self.node_target = KnowledgeNode.objects.create(
            user=self.user,
            name='Django 进阶',
            node_type='skill',
            mastery_level=0,
        )
        self.node_target.prerequisites.add(self.node_prereq)

        self.path = LearningPath.objects.create(
            user=self.user,
            title='Web 开发路径',
            description='从 Python 到 Django',
            target_node=self.node_target,
        )
        self.path_node_prereq = PathNode.objects.create(
            path=self.path,
            node=self.node_prereq,
            order=1,
            status='locked',
        )
        self.path_node_target = PathNode.objects.create(
            path=self.path,
            node=self.node_target,
            order=2,
            status='locked',
        )

    def test_handle_study_record_updates_progress_and_creates_reviews(self):
        record = StudyRecord.objects.create(
            user=self.user,
            node=self.node_prereq,
            duration_minutes=30,
        )
        service = LearningProgressService(self.user)
        service.handle_study_record(record)

        self.node_prereq.refresh_from_db()
        self.path_node_prereq.refresh_from_db()
        self.path_node_target.refresh_from_db()
        self.path.refresh_from_db()

        self.assertEqual(self.node_prereq.mastery_level, 16)
        self.assertEqual(self.path_node_prereq.progress, 16)
        self.assertEqual(self.path_node_prereq.status, 'in_progress')
        self.assertEqual(self.path.status, 'in_progress')
        self.assertEqual(self.path_node_target.status, 'locked')

        tasks = ReviewTask.objects.filter(user=self.user, node=self.node_prereq).order_by('scheduled_date')
        self.assertEqual(tasks.count(), 3)
        deltas = {
            (task.scheduled_date - record.created_at.date()).days
            for task in tasks
        }
        self.assertSetEqual(deltas, {1, 3, 5})

    def test_handle_study_record_unblocks_next_node_when_mastered(self):
        # 让前置节点已学习到 70 分
        self.node_prereq.mastery_level = 70
        self.node_prereq.save()
        self.path_node_prereq.status = 'in_progress'
        self.path_node_prereq.progress = 70
        self.path_node_prereq.started_at = timezone.now() - timedelta(days=1)
        self.path_node_prereq.save()

        record = StudyRecord.objects.create(
            user=self.user,
            node=self.node_prereq,
            duration_minutes=90,  # capped to +20 mastery, 70 -> 90
        )

        service = LearningProgressService(self.user)
        service.handle_study_record(record)

        self.path_node_prereq.refresh_from_db()
        self.path_node_target.refresh_from_db()
        self.path.refresh_from_db()

        self.assertEqual(self.node_prereq.mastery_level, 90)
        self.assertEqual(self.path_node_prereq.status, 'completed')
        self.assertIsNotNone(self.path_node_prereq.completed_at)
        self.assertEqual(self.path_node_target.status, 'available')
        self.assertIsNotNone(self.path_node_target.started_at)
        self.assertEqual(self.path.progress, 45)  # 平均(90 + 0) = 45

        tasks = ReviewTask.objects.filter(user=self.user, node=self.node_prereq).order_by('scheduled_date')
        deltas = {
            (task.scheduled_date - record.created_at.date()).days
            for task in tasks
        }
        self.assertSetEqual(deltas, {14, 30, 60})

    def test_handle_short_session_still_raises_mastery(self):
        record = StudyRecord.objects.create(
            user=self.user,
            node=self.node_target,
            duration_minutes=0,
        )
        service = LearningProgressService(self.user)
        service.handle_study_record(record)

        self.node_target.refresh_from_db()
        self.assertEqual(self.node_target.mastery_level, 2)  # min gain defined in service

    def test_update_multiple_paths_for_same_node(self):
        # 第二条学习路径也包含 node_prereq
        another_path = LearningPath.objects.create(
            user=self.user,
            title='算法路径',
            description='',
            target_node=self.node_prereq,
        )
        PathNode.objects.create(
            path=another_path,
            node=self.node_prereq,
            order=1,
            status='locked',
        )

        record = StudyRecord.objects.create(
            user=self.user,
            node=self.node_prereq,
            duration_minutes=60,
        )
        service = LearningProgressService(self.user)
        affected = service.handle_study_record(record)

        self.assertEqual(len(affected), 2)
        for path_id in affected:
            path = LearningPath.objects.get(id=path_id)
            self.assertEqual(path.status, 'in_progress')
            self.assertGreater(path.progress, 0)


class ReviewSchedulerTests(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username='scheduler', email='sch@example.com', password='pwd123456')
        self.node = KnowledgeNode.objects.create(
            user=self.user,
            name='算法',
            node_type='topic',
            mastery_level=50,
        )

    def test_scheduler_is_idempotent(self):
        scheduler = ReviewScheduler(self.user)
        scheduler.schedule_for_node(self.node, reference_date=timezone.now().date())
        scheduler.schedule_for_node(self.node, reference_date=timezone.now().date())

        tasks = ReviewTask.objects.filter(user=self.user, node=self.node)
        self.assertEqual(tasks.count(), 3)
        self.assertTrue(all(task.status == 'pending' for task in tasks))

    def test_scheduler_completes_overdue_tasks(self):
        past_date = timezone.now().date() - timedelta(days=5)
        # 创建一个已过期的任务
        ReviewTask.objects.create(
            user=self.user,
            node=self.node,
            scheduled_date=past_date,
            status='pending',
        )
        scheduler = ReviewScheduler(self.user)
        scheduler.schedule_for_node(self.node, reference_date=timezone.now().date())

        refreshed_task = ReviewTask.objects.get(user=self.user, node=self.node, scheduled_date=past_date)
        self.assertEqual(refreshed_task.status, 'completed')
        self.assertIsNotNone(refreshed_task.completed_at)

    def test_intervals_for_various_mastery_levels(self):
        scheduler = ReviewScheduler(self.user)

        # mastery <30
        self.node.mastery_level = 20
        self.node.save()
        scheduler.schedule_for_node(self.node, reference_date=timezone.now().date())
        tasks_low = sorted(
            ReviewTask.objects.filter(node=self.node).values_list('scheduled_date', flat=True)
        )
        self.assertEqual(
            { (date - timezone.now().date()).days for date in tasks_low },
            {1, 3, 5}
        )

        ReviewTask.objects.filter(node=self.node).delete()

        # mastery 60-85
        self.node.mastery_level = 70
        self.node.save()
        scheduler.schedule_for_node(self.node, reference_date=timezone.now().date())
        tasks_mid = sorted(
            ReviewTask.objects.filter(node=self.node).values_list('scheduled_date', flat=True)
        )
        self.assertEqual(
            { (date - timezone.now().date()).days for date in tasks_mid },
            {7, 14, 30}
        )
